module("TcpServer",package.seeall)
local socket = require("socket")


local function Str2Int(str,offset)
    if string.len(str)-offset < 4 then
        return 0;
    end
    local a,b,c,d = string.byte(str,1+offset,4+offset);
    if d == nil then
        d = 0;
    end
    return a + b * 256 + c * 65536 + d * 16777216;
end

local function Int2Str(int)
    local ret = "";
    local z = string.format("\0");

    local i = 1;
    while i <= 16777216 do
        local b = (int/i)%256;
        if b < 1 then
            ret = ret..z;
        else
            ret = ret..string.format("%c",b);
        end
        i = i * 256;
    end
    return ret;
end

-- simple set implementation
-- the select function doesn't care about what is passed to it as long as
-- it behaves like a table
-- creates a new set data structure
local function newset()
    local reverse = {}
    local set = {}
    return setmetatable(set, {__index = {
        insert = function(set, value)
            if not reverse[value] then
                table.insert(set, value)
                reverse[value] = table.getn(set)
            end
        end,
        remove = function(set, value)
            local index = reverse[value]
            if index then
                reverse[value] = nil
                local top = table.remove(set)
                if top ~= value then
                    reverse[top] = index
                    set[index] = top
                end
            end
        end
    }})
end

local m_fd = 1;
local m_set = newset();
local m_ports = {};
local m_clientport = {};
local m_socketfd = {};
local m_OnConnect;
local m_OnRecv;
local m_OnDisconnect;
local m_IsStopping;
---------------------------------------

function Send(fd,sendmsg,nLen)
    local sock = m_socketfd[fd];
    if not sock then
        return;
    end

    local conf = m_clientport[sock];
    if conf[3] ~= nil and conf[3] == 1 then
        sock:send(Int2Str(nLen));
    end
    sock:send(sendmsg);
end

function Disconnect(fd)
    local sock = m_socketfd[fd];
    if not sock then
        return;
    end
    m_socketfd[fd] = nil;
    sock:close()
    m_set:remove(sock)
    m_clientport[sock] = nil;
end


function Run(ports,OnConnect,OnRecv,OnDisconnect,IsStopping)
    m_OnConnect = OnConnect;
    m_OnRecv = OnRecv;
    m_OnDisconnect = OnDisconnect;
	m_IsStopping = IsStopping;

    for _,conf in ipairs(ports) do
        local server = assert(socket.bind("*", conf[1]));
        server:settimeout(1) -- make sure we don't block in accept
        m_set:insert(server)
        m_ports[server] = conf;
        print(conf[1]);
    end

    while true do
        if m_IsStopping and m_IsStopping() then
            break
        end
        local readable, _, error = socket.select(m_set, nil,1)
		if error == nil then
			for _, input in ipairs(readable) do
				local conf = m_ports[input];
				-- is it a server socket?
				if conf then
					local new = input:accept()
					if new then
						new:settimeout(1)
						m_fd = m_fd + 1;
						local fd = m_fd;
						m_set:insert(new)
						m_clientport[new] = {conf[1],fd,conf[3]};
						m_socketfd[fd] = new;
						if m_OnConnect then
							m_OnConnect(fd,conf[1]);
						end
					end
				-- it is a client socket
				else
					if m_OnRecv then
						local conf = m_clientport[input];
						if conf[3] ~= nil and conf[3] == 1 then
							input:settimeout(1,'b')
							local line, error = input:receive(4);
							if error then
								input:close()
								if OnDisconnect then
									OnDisconnect(conf[2],conf[1]);
								end
								m_set:remove(input)
								m_clientport[input] = nil;
							else
								local nlen = Str2Int(line,0);
								if nlen > 0 then
									input:settimeout(100,'b')
									line, error = input:receive(nlen);
									input:settimeout(1,'b')
									if error then
										input:close()
										if OnDisconnect then
											OnDisconnect(conf[2],conf[1]);
										end
										m_set:remove(input)
										m_clientport[input] = nil;
									else
										m_OnRecv(conf[2],conf[1],line,nlen);
									end
								end
							end
						else
							input:settimeout(0,'b')
							local line = "";
							local c;
							local error;
							while true do
								c, error = input:receive(1);
								if error ~= nil then
									break;
								end
								line = line .. c;
							end
							input:settimeout(100,'b')

							if error and error == "closed" then
								input:close()
								if OnDisconnect then
									OnDisconnect(conf[2],conf[1]);
								end
								m_set:remove(input)
								m_clientport[input] = nil;
							elseif line ~= nil then
								m_OnRecv(conf[2],conf[1],line,string.len(line));
							end
						end
					end
				end
			end
		end
    end

    for _, input in ipairs(m_set) do
        input:close()
    end
end
